using System;
using System.Collections;
using System.IO;

namespace YACLAP
{
    /// <summary>
    /// "Yet Another Command Line Argument Parser"
    /// By Sean Michael Murphy. 
    /// http://www.codeproject.com/csharp/YACLAP.asp
    /// The main class in the library, and the only one that's creatable.
    /// </summary>
    public class ArgParser : IDisposable
    {
        private const string FILE_SPECIFIER = "@";
        private VVPairs _pairs = new VVPairs();

        #region ctors

        /// <summary>
        /// Constructor to be called when only the parameter array needs to be
        /// supplied.  It just calls the more elaborate constructor with the
        /// default variable specifier character and value separator characters.
        /// </summary>
        /// <param name="args">String array passed to the running executable from the
        /// command shell.</param>
        public ArgParser(string[] args) : this(args, '/', ':')
        {
        }

        /// <summary>
        /// Constructor called when the non-default variable specifier character
        /// and value separator characters are wanted.
        /// </summary>
        /// <param name="args">String array passed to the running executable from the
        /// command shell.</param>
        /// <param name="argDelimitter">A character used to identify the start of a 
        /// variable name in the argument list.</param>
        /// <param name="valueDelimitter">A character used to identify the start
        /// of the variable value in the variable-value pair string.</param>
        public ArgParser
            (string[] args,
             char argDelimiter,
             char valueDelimiter)
        {
            if (args.Length > 0)
            {
                var parameters = new ArrayList();

                // Do a pass through the parameters, looking for argument 
                // definition files.  Phase I pass.
                foreach (string rawData in args)
                {
                    if (rawData.IndexOf(FILE_SPECIFIER) == 0)
                    {
                        // A file has been found
                        string file = rawData.Substring(1);

                        if (File.Exists(file))
                            ParseFile(file, argDelimiter, parameters);
                    }
                    else // Doesn't start with "@".
                        if (rawData.Trim().IndexOf(argDelimiter.ToString()) == 0)
                            // A regular parameter.  Add it to the list to parse.
                            // Trim off the first character, which just marks it
                            // as the start of a variable.
                            parameters.Add(rawData.Substring(1));
                }

                // Now the command line is fully expanded from any parameter files
                // that may have been present into the arraylist.
                foreach (string rawData in parameters)
                {
                    if (rawData.Length > 0)
                    {
                        Int32 end = rawData.IndexOf(valueDelimiter.ToString());
                        string variable = string.Empty;
                        string value = string.Empty;

                        // Split the variable name from the value, using the 
                        // parameter delimiter to decide where in the raw string.
                        if (end > 0)
                        {
                            variable = rawData.Substring(0, end);

                            if ((end + 1) < rawData.Length)
                                value = rawData.Substring(end + 1);
                        }
                        else
                            variable = rawData;

                        _pairs.Add(variable, value);
                    }
                }
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Function to determine if a parameter exists in the current list.
        /// </summary>
        /// <param name="srVariable">The name of the parameter to search for.</param>
        /// <returns>True if it is in the list, false otherwise.</returns>
        public bool Exists
            (string variable)
        {
            foreach (VVPair pair in _pairs)
                if (pair.Variable == variable)
                    return true;

            return false;
        }

        #endregion

        #region Private Methods

        private void ParseFile
            (string fileName,
             char argDelimiter,
             ArrayList parameters)
        {
            var streamFile = new StreamReader(fileName);
            string[] RSPFileData = streamFile.ReadToEnd().Split('\n');

            streamFile.Close();

            // Go through the file, and expand the listed parameters
            // into the ArrayList of existing parameters.
            foreach (string line in RSPFileData)
            {
                string trim = line.Trim();

                if (trim.IndexOf(argDelimiter.ToString()) == 0)
                    // A variable found.  Trim off the variable
                    // marker character.
                    parameters.Add(trim.Substring(1));
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Accessor method to get the value of an expected parameter
        /// from its name.  The accessor method is usually on the collection
        /// class, but it isn't exposed anywhere.  It's put here as a shortcut.
        /// </summary>
        public VVPair this[string variable]
        {
            get { return _pairs[variable]; }
        }

        /// <summary>
        /// Indexer to allow the parameters to be read by index, rather
        /// than name.  Just returns the collection class numerical indexer.
        /// </summary>
        public VVPair this[Int32 index]
        {
            get { return _pairs[index]; }
        }

        public string GetValueOrDefault(string variable, string defaultValue)
        {
            string currentValue = _pairs[variable].Value;
            if (string.IsNullOrEmpty(currentValue))
            {
                return defaultValue;
            }
            else
            {
                return currentValue;
            }
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Called when the class is to be destroyed.
        /// </summary>
        public void Dispose()
        {
            _pairs.Dispose();
            _pairs = null;
        }

        #endregion
    }

    /// <summary>
    /// Class used to hold the variable name and value of the 
    /// command line parameter.
    /// </summary>
    public class VVPair : IDisposable
    {
        private readonly string _variable = string.Empty;
        private string _value = string.Empty;

        #region ctors

        /// <summary>
        /// Default constructor.  Can only be created by the YACLAP
        /// namespace.
        /// </summary>
        internal VVPair()
        {
        }

        /// <summary>
        /// Constructor called when the name of the variable and its value
        /// is known.
        /// </summary>
        /// <param name="srVariable">The name of the variable to add.</param>
        /// <param name="srValue">The value of the variable.</param>
        internal VVPair
            (string variable,
             string value) : this()
        {
            _variable = variable;
            _value = value;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Name of the variable.  Can only be set by the constructor.
        /// </summary>
        public string Variable
        {
            get { return _variable; }
        }

        /// <summary>
        /// Value of the variable.  Can be set after construction.
        /// </summary>
        public string Value
        {
            get { return _value; }
            set { _value = value; }
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Do nothing.  No objects to destroy.
        /// </summary>
        public void Dispose()
        {
        }

        #endregion
    }

    /// <summary>
    /// Collection class of variable-value pair objects. Only the Add() method
    /// is overridden.  It's internal because it's never exposed as a property
    /// or parameter anywhere in the library.
    /// </summary>
    internal class VVPairs : CollectionBase, IDisposable
    {
        #region ctors

        #endregion

        #region Public Methods

        /// <summary>
        /// Method called to add a new variable-value argument pair to 
        /// the collection.
        /// </summary>
        /// <param name="variable">The name of the variable to add.</param>
        /// <param name="value">The value of the variable.</param>
        public void Add
            (string variable,
             string value)
        {
            // Check to see if the variable exists already.  If it does,
            // change the value and return.
            foreach (VVPair pair in List)
            {
                if (pair.Variable == variable)
                {
                    pair.Value = value;

                    return;
                }
            }

            // Otherwise add it.
            var newPair = new VVPair(variable, value);

            List.Add(newPair);
        }

        #endregion

        #region Accessors

        public VVPair this[string variable]
        {
            get
            {
                foreach (VVPair pair in List)
                    if (pair.Variable == variable)
                        return pair;

                // If the name wasn't found in the collection, return an
                // empty object so the caller doesn't have to trap a null.
                // They'll just get a zero-length string for the value.
                return new VVPair();
            }
        }

        public VVPair this[Int32 index]
        {
            get { return (VVPair) List[index]; }
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Destroy the variable value pair objects in the collection.
        /// </summary>
        public void Dispose()
        {
            foreach (VVPair pair in List)
                pair.Dispose();

            List.Clear();
        }

        #endregion
    }
}